Traits
Traitler
yani türkçe çevirisi ile kişisel özellikler
bir tipin yapabildiği ve diğer tiplere paylaşabildiği özellikleri ifade etmektedir. Traitleri
ortaklaşılmış özellikleri soyut olarak kullanmak için tanımlayabiliriz. Hadi başlayalım.
İlk yapmamız gereken trait
bloğunu trait anahtar kelimesi ve istediğimiz isimle oluştururuz.
trait Gozlemle{
}
Oluşturduğumuz bu trait içerisine istediğimiz methodun neler alacağını ve ne çıktı vereceğini tanıtmamız gerekmektedir.
trait Overview{
fn Overview(&self) -> String;
}
Yukarıda ifade edilidiği üzere fonksiyonun girdisini ve çıktısını tanımladık. Şimdi hadi bu fonksiyonu ortak olarak kapsayacak struct yapılarını oluşturalım:
struct Course {
headline: String,
author: String
}
struct AnotherCourse {
headline: String,
author: String
}
İki adet struct yapısını oluşturduk. Artık bu yapıların içerisine trait
yapımızı impl
ile implement edebiliriz.
impl Overview for Course{
fn overview(&self) -> String {
format!("{} {}", self.author, self.headline)
}
}
impl Overview for AnotherCourse{
fn overview(&self) -> String {
format!("{} {}", self.author, self.headline)
}
}
Yukarıda da görüntüleyebileceğiniz gibi overview
traits yapısını iki struct için de ekledik.
Farklı bir özellik olarak bu traits yapıları içerisine defult değerler de tanımlamak mümkündür.
trait Overview{
fn Overview(&self) -> String {
String::from("Bu bir Rust Kursudur!")
}
}
Sonrasında bu kurları değişken olarak oluşturabiliriz.
let course1 = Course(headline: String::from("Hedline", author: String::from("Aybars")))
let course2 = AnotherCourse(headline: String::from("Another Hedline", author: String::from("İrem ")))
Sonrasında bu yazdıklarımız içerisindeki fonksiyonu kullanabiliriz:
println!("{}", course1.overview());
println!("{}", course2.overview());
Çalıştırdığımızda alt kısımdaki bir çıktı çıkacak ve bu kısımda defult
olarak eklediğimiz çıktının olmadığını görüntüleyebilirsiniz.
Aybars, headline,
İrem, another hadline
Bu durumun nedeni bizim aslında impl
içerisinde overwrite yapıyor olmamızdır. Eğer bu yapıların içerisini temizlersek:
impl Overview for Course{}
impl Overview for AnotherCourse{}
Şu şekilde bir sonuç ile karşılaşırız:
Bu bir Rust Kursudur!
Bu bir Rust Kursudur!
## Trait Değerlerini Parametre Olarak Girmek
Trait değerlerini birer parametre olarak da kullanmamız mümkündür. İlk olarak bir foksiyon oluşturarak bu işleme başlayabiliriz.
fn call_overview(item: &impl Overview){
println!("Overview: {}", item.overview())
}
Sonrasında bu fonksiyou çağırabiliriz.
call_overview(&course1);
call_overview(&course2);
Aynı zamanda generics
kullanarak fonksiyonu biraz daha basitleştirebiliriz.
fn call_overview<T: Overview>(item: &T){
println!("Overview: {}", item.overview())
}
Şimdi birazcık geriye doğru açılalım ve yaptıklarımızı düşünelim. Traitler aslında veri tiplerine fonksiyon eklememizi sağlayan bir yapı. E diyebilirsiniz ki bunu zaten struct methodları yapmıyor mu? Yapıyor ancak tek bir yapı türüne yapıyor. Traitler ile oluşturduğumuz bu özelliği birden fazla türe aktarabiliriz. Şöyle düşünelim. Bizim bir fabrikamız var ve bu fabrikada araba, uçak, motorsiklet üretiyorsunuz. Bunların hepsinin modelleri var. Ama aynı zamanda her birinin bir ortak özelliği de var. Her bir araba modeli için yeniden tekerlek kodu yazmaktansa bir tekerlek kodunu trait
olarak tanımlayarsak sonrasına bu trait
değerini tüm araba structlarımıza verebiliriz.
Şimdi biraz daha iyi anladıysak bu iki fonksiyonun birbirinden farkına bakalım. İlk yazdığımız fonksiyon türünde her bir item için istediğimiz türü kullanabilecekken, Generics ile yazdığımız ikinci versiyonda ise hangi türü verirsen ver bu tür tüm diğer itemler için ortak olmalıdır.
fn overview(item1: &impl Overview, item2: &impl Overview) // item1 ve item2 nin tipi aynı olmak zorunda değil
fn overview<T: Overview>(item1: &T, item2: &T) // item1 ve item2 nin tipi aynı olmak zorunda
Aynı zamanda birden çok trait de kullanabiliriz. Bunu da inceleyebilirsiniz.
Utility Traitleri
Bu dersimizde utility traits
yani türkçe çevirisi ile faydalı özellikler
'i öğreneceğiz. Bu özellikler standart olarak Rust içerisinde bulunan ve Rust kodlarının yazımında büyük önem arz eden bu nedenle de öğrenmemiz gereken yapılardır.
1. Drop Trait
Aslında kursun içerisinde bu kelimenin çokça kullanıldığını duymuş olabiliriz. Rust içerisinde bu kavarmı konuşunca da bir değişkeni değerinden kurtarmak olarak düşünürüz.
Bu drop
işlemleri birçok yerde gerçekleşir. Bir değişkenin scope dışı olması veya bitmesi bu duruma örnek olarak verilebilir. Genelde Rust bunu bizim için otamatik yapsa da bizde nasıl kullanacağımızı bilmek isteyebiliriz.
Bu yapıyı oluşturmak için struct yapısı içeisine bir method şeklinde ekleme yapabiliriz.
impl Drop for Course{
fn drop(&mut self){
println!("Dropping: {}")
}
}
Oluşturduğumuz bu drop
fonksiyonu özel bir fonksiyondur. Direkt olarak çağırabilir.
drop(course1)
Fonksiyonumuzu çalıştırdığımuzda ekrana yazdırıldığını görebiliriz. Ancak bu yapıyı hiç kullanmasak da aynı şekilde bir çıktıyı alacağız. Bu durumun nedeni course1
değerinin kod bloğu dışında drop
olmasından dolayı aynı çıktının oluştuğunu görüntüleyebiliriz.
2. Clone Trait
Clone Trait
kendisinin kopyasını oluşturabilen tipler için olmaktadır. Bu trait içerisinde çalışacak kodların oluşabilmesi için self
ibaresine girilen değerin harici bir clone'unun oluşması gerekmektedir.
3. Copy Trait
Daha önce de copy
yapısını konuşmuş olsak da şimdi biraz daha ayrıntılı bakabiliriz. i32 gibi basit ve herhangi bir kaynak gerektirmeyen değerler basit bir şekilde kopyalanabilir. Bu demek olmaktadır ki içerisinde kendinden farklı bir kaynağı barındıran hiçbir tip copy type
yani kopyalanabilir tür olamaz.
From / into traitleri ve Operating Overloading de incelenebilir.